﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace PokemonRPG
{
    public class Terrain
    {
        Game game;
        int width, length;
        float maxHeight;

        Effect gridEffect;
        Texture2D tex;
        float[] hData;
        Texture2D hMap;
        Texture2D nMap;
        VertexBuffer gridVB;
        IndexBuffer gridIB;

        Vector3 lightDir;

        public Terrain(Game game, int width, int length)
        {
            this.game = game;
            this.width = width;
            this.length = length;
            this.maxHeight = 4;

            lightDir = Vector3.Up;
        }

        public void LoadContent()
        {
            gridEffect = game.Content.Load<Effect>("Terrain");
            gridEffect.Parameters["MaxHeight"].SetValue(maxHeight);

            tex = game.Content.Load<Texture2D>("Ground");
            gridEffect.Parameters["Texture"].SetValue(tex);

            hMap = game.Content.Load<Texture2D>("Height Map");
            gridEffect.Parameters["HeightMap"].SetValue(hMap);
            hData = new float[hMap.Width * hMap.Height];
            hMap.GetData(hData);

            nMap = game.Content.Load<Texture2D>("Normal Map");
            gridEffect.Parameters["NormalMap"].SetValue(nMap);

            //// Normal Map Generation Code
            //float mapStep = ((float)width / hMap.Width);

            //var nData = new Vector4[hData.Length];
            //for (int i = 0; i < hMap.Width; i++)
            //    for (int j = 0; j < hMap.Height; j++)
            //    {
            //        Vector3 norm = Vector3.Up;

            //        float rotZ = (float)Math.Atan((getHeightAt(i + 1, j) - getHeightAt(i - 1, j)) / (mapStep * 2f));
            //        float rotX = (float)Math.Atan((getHeightAt(i, j + 1) - getHeightAt(i, j - 1)) / (mapStep * 2f));
            //        norm = Vector3.Transform(norm, Matrix.CreateRotationX(rotX) * Matrix.CreateRotationZ(rotZ));

            //        norm = norm / 2 + new Vector3(0.5f);
            //        nData[i + j * hMap.Width] = new Vector4(norm, 1);
            //    }

            //var nTex = new Texture2D(game.GraphicsDevice, hMap.Width, hMap.Height, false, SurfaceFormat.Vector4);
            //nTex.SetData(nData);
            //var fs = System.IO.File.OpenWrite("..\\..\\..\\..\\PokemonRPGContent\\Normal Map.png");
            //nTex.SaveAsPng(fs, nTex.Width, nTex.Height);
            //fs.Close();

            //game.Exit();

            int w = hMap.Width;
            int l = hMap.Height;

            var gridverts = new VertexPositionTexture[w * l];
            for (int i = 0; i < gridverts.Length; i++)
            {
                float x = i % w;
                float y = i / w;
                gridverts[i] = new VertexPositionTexture(new Vector3(x * width / hMap.Width, 0, y * length / hMap.Height),
                    new Vector2(x / (w - 1), y / (l - 1)));
            }
            gridVB = new VertexBuffer(game.GraphicsDevice, VertexPositionTexture.VertexDeclaration, gridverts.Length, BufferUsage.None);
            gridVB.SetData(gridverts);

            var gridIndxes = new int[(w - 1) * (l - 1) * 6];
            for (int i = 0, offset = 0; i < gridIndxes.Length; i += 6)
            {
                int v = i / 6;

                if (((v + offset) % w) == (w - 1)) offset++;
                v += offset;

                gridIndxes[i + 0] = v;
                gridIndxes[i + 1] = v + 1;
                gridIndxes[i + 2] = v + w + 1;

                gridIndxes[i + 3] = v;
                gridIndxes[i + 4] = v + w + 1;
                gridIndxes[i + 5] = v + w;
            }
            gridIB = new IndexBuffer(game.GraphicsDevice, IndexElementSize.ThirtyTwoBits, gridIndxes.Length, BufferUsage.None);
            gridIB.SetData(gridIndxes);

            //game.GraphicsDevice.RasterizerState = new RasterizerState() { FillMode = FillMode.WireFrame };
        }

        public float GetHeightAt(Vector3 position)
        {
            return GetHeightAt(position.X, position.Z);
        }

        public float GetHeightAt(float x, float y)
        {
            int i = (int)(x / width * hMap.Width);
            int j = (int)(y / length * hMap.Height);

            float xOffset = x / width * hMap.Width % 1;
            float yOffset = y / length * hMap.Height % 1;

            // Bi-Linear Interpolation!!!
            return MathHelper.Lerp(
                MathHelper.Lerp(getHeightAt(i + 0, j + 0), getHeightAt(i + 1, j + 0), xOffset),
                MathHelper.Lerp(getHeightAt(i + 0, j + 1), getHeightAt(i + 1, j + 1), xOffset),
                yOffset);
        }

        private float getHeightAt(int i, int j)
        {
            if (i < 0 || j < 0 || i >= hMap.Width || j >= hMap.Height) return 0;

            return hData[j * hMap.Width + i] * maxHeight;
        }

        public void Draw(Camera cam)
        {
            lightDir = Vector3.Transform(Vector3.Up, Matrix.CreateRotationZ(
                (float)DateTime.Now.TimeOfDay.TotalSeconds * MathHelper.TwoPi / 10f));
            //if (Microsoft.Xna.Framework.Input.Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.X))
            //    lightDir = Vector3.Transform(lightDir, Matrix.CreateRotationZ(-0.02f));
            //if (Microsoft.Xna.Framework.Input.Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Z))
            //    lightDir = Vector3.Transform(lightDir, Matrix.CreateRotationZ(+0.02f));

            gridEffect.Parameters["View"].SetValue(cam.View);
            gridEffect.Parameters["Projection"].SetValue(cam.Projection);
            gridEffect.Parameters["World"].SetValue(Matrix.Identity);
            gridEffect.Parameters["LightDir"].SetValue(lightDir);
            gridEffect.CurrentTechnique.Passes[0].Apply();

            game.GraphicsDevice.SetVertexBuffer(gridVB);
            game.GraphicsDevice.Indices = gridIB;
            game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, gridVB.VertexCount, 0, gridIB.IndexCount / 3);
        }
    }
}
